using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Diagnostics;
using Look_Through_Windows.Helper;

namespace Look_Through_Windows.Network
{
    public class NetMessage
    {
        private int position = 0;
        private List<byte> data;
        public NetConnection Sender;

        private Stack<int> posstack;

        public bool EndOfMessage { get { return position >= data.Count; } }

        public void Save( )
        {
            if ( posstack == null ) posstack = new Stack<int>( );
            posstack.Push( position );
        }

        public void Load( )
        {
            if ( posstack == null || posstack.Count == 0 ) throw new InvalidOperationException( );
            position = posstack.Pop( );
        }

        private NetMessage( )
        {
            data = new List<byte>( );
        }

        public NetMessage( byte sendcode )
        {
            data = new List<byte>( );
            Write( sendcode, "Code" );
        }

        internal NetMessage( List<byte> data )
        {
            this.data = data;
        }

        public void Write( byte val, string field ) { WriteFieldName( field + " [byte]" ); data.Add( val ); }

        public void Write( bool val, string field ) { WriteFieldName( field + " [bool]" ); data.Add( val ? ( byte )1 : ( byte )0 ); }

        public void Write( byte[] val, string field ) { WriteFieldName( field + " [byte[]]" ); Write( val.Length, field + " length" ); data.AddRange( val ); }

        public void Write( short val, string field ) { WriteFieldName( field + " [short]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( char val, string field ) { WriteFieldName( field + " [char]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( int val, string field ) { WriteFieldName( field + " [int]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( long val, string field ) { WriteFieldName( field + " [long]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( DateTime val, string field ) { WriteFieldName( field + " [DateTime]" ); data.AddRange( BitConverter.GetBytes( val.Ticks ) ); }

        public void Write( ushort val, string field ) { WriteFieldName( field + " [ushort]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( uint val, string field ) { WriteFieldName( field + " [uint]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( ulong val, string field ) { WriteFieldName( field + " [ulong]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( float val, string field ) { WriteFieldName( field + " [float]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( double val, string field ) { WriteFieldName( field + " [double]" ); data.AddRange( BitConverter.GetBytes( val ) ); }

        public void Write( List<uint> val, string field )
        {
            WriteFieldName( field + " [List<uint>]" );
            Write( val.Count, "count" );
            foreach ( var i in val )
                data.AddRange( BitConverter.GetBytes( i ) );
        }
        public void Write<T>( List<T> val, string field ) where T : struct
        {
            WriteFieldName( field + " [List<" + typeof( T ).Name + ">]" );
            Write( val.Count, "count" );
            foreach ( var t in val )
                data.AddRange( Helpers.RawSerialize( t ) );
        }

        public void Write( PointF val, string field )
        {
            WriteFieldName( field + " [PointF]" );
            data.AddRange( BitConverter.GetBytes( val.X ) );
            data.AddRange( BitConverter.GetBytes( val.Y ) );
        }
        public void Write( Point val, string field )
        {
            WriteFieldName( field + " [Point]" );
            data.AddRange( BitConverter.GetBytes( val.X ) );
            data.AddRange( BitConverter.GetBytes( val.Y ) );
        }
        public void Write( SizeF val, string field )
        {
            WriteFieldName( field + " [SizeF]" );
            data.AddRange( BitConverter.GetBytes( val.Width ) );
            data.AddRange( BitConverter.GetBytes( val.Height ) );
        }
        public void Write( Size val, string field )
        {
            WriteFieldName( field + " [Size]" );
            data.AddRange( BitConverter.GetBytes( val.Width ) );
            data.AddRange( BitConverter.GetBytes( val.Height ) );
        }

        public void Write( RectangleF val, string field )
        {
            WriteFieldName( field + " [RectangleF]" );
            data.AddRange( BitConverter.GetBytes( val.Left ) );
            data.AddRange( BitConverter.GetBytes( val.Top ) );
            data.AddRange( BitConverter.GetBytes( val.Width ) );
            data.AddRange( BitConverter.GetBytes( val.Height ) );
        }
        public void Write( Rectangle val, string field )
        {
            WriteFieldName( field + " [Rectangle]" );
            data.AddRange( BitConverter.GetBytes( val.Left ) );
            data.AddRange( BitConverter.GetBytes( val.Top ) );
            data.AddRange( BitConverter.GetBytes( val.Width ) );
            data.AddRange( BitConverter.GetBytes( val.Height ) );
        }
        public void Write( Bitmap bmp, string field )
        {
            WriteFieldName( field + " [Bitmap]" );

            MemoryStream ms = new MemoryStream( );
            bmp.Save( ms, ImageFormat.Png );
            //Console.WriteLine( ms.Length );
            Write( ms.ToArray( ), "Bmp data" );

            /*
            int width = bmp.Width;
            int height = bmp.Height;
            byte[] colors = new byte[ width * height * 4 ];
            var bitmapData = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.ReadOnly, bmp.PixelFormat );
            Marshal.Copy( bitmapData.Scan0, colors, 0, width * height * 4 );
            bmp.UnlockBits( bitmapData );
            Write( width, field + " width" );
            Write( height, field + " height" );
            data.AddRange( colors );
            */
        }

        public void Write( string msg, string field ) { WriteFieldName( field + " [string]" ); foreach ( char c in msg ) data.Add( ( byte )c ); data.Add( 0 ); }

        public void Write<T>( T obj, string field ) where T : class
        {
            WriteFieldName( field + " [<" + typeof( T ).Name + ">]" );
            Write( obj == null, field + " is null" );
            if ( obj != null )
            {
                byte[] ser = SerializeIt( obj );
                Write( ser, field + " data" );
            }
        }

        public void Write( double[] val, string field )
        {
            WriteFieldName( field + " [double[]]" );
            Write( val.GetLength( 0 ), field + " length" );
            for ( int i = 0 ; i < val.Length ; ++i )
                data.AddRange( BitConverter.GetBytes( val[ i ] ) );
        }
        public void Write( double[ , ] val, string field )
        {
            WriteFieldName( field + " [double[,]]" );
            Write( val.GetLength( 0 ), field + " length X" );
            Write( val.GetLength( 1 ), field + " length Y" );
            int width = val.GetLength( 0 );
            int height = val.GetLength( 1 );
            for ( int y = 0 ; y < height ; ++y )
                for ( int x = 0 ; x < width ; ++x )
                    data.AddRange( BitConverter.GetBytes( val[ y, x ] ) );
        }


        [Conditional( "DEBUG" )]
        private void WriteFieldName( string field ) { foreach ( char c in field ) data.Add( ( byte )c ); data.Add( 0 ); }
        [Conditional( "DEBUG" )]
        private void CheckFieldName( string field )
        {
            if ( position >= data.Count ) throw new Exception( "End of Message Error" );

            StringBuilder builder = new StringBuilder( );
            byte b = 0;
            while ( position < data.Count && ( b = data[ position++ ] ) != 0 )
                builder.Append( ( char )b );
            //position++;
            if ( builder.ToString( ) != field ) throw new Exception( "Field error! found: " + builder + ", expected: " + field );
        }

        public Bitmap ReadBitmap( string field )
        {
            CheckFieldName( field + " [Bitmap]" );

            byte[] comprBytes = ReadByteArray( "Bmp data" );
            return new Bitmap( new MemoryStream( comprBytes ) );

            /*if ( position >= data.Count - 4 - 4 + 1 ) throw new Exception( "End of Message Error" );
            int width = ReadInt( field + " width" );
            int height = ReadInt( field + " height" );
            int length = width * height * 4;
            if ( position >= data.Count - length + 1 ) throw new Exception( "End of Message Error" );
            byte[] colors = new byte[ length ];
            for ( int i = 0 ; i < length ; i++ )
                colors[ i ] = data[ position++ ];
            var bmp = new Bitmap( width, height );
            bmp.Fill( colors );
            return bmp;*/
        }

        public List<uint> ReadUIntList( string field )
        {
            CheckFieldName( field + " [List<uint>]" );
            int count = ReadInt( "count" );
            if ( position >= data.Count - count * 4 + 1 ) throw new Exception( "End of Message Error" );
            List<uint> output = new List<uint>( );
            for ( int i = 0 ; i < count ; ++i )
                output.Add( ( uint )data[ position++ ] +
                ( ( uint )data[ position++ ] << 8 ) +
                ( ( uint )data[ position++ ] << 16 ) +
                ( ( uint )data[ position++ ] << 24 ) );
            return output;
        }
        public List<T> ReadStructList<T>( string field ) where T : struct
        {
            CheckFieldName( field + " [List<" + typeof( T ).Name + ">]" );
            int count = ReadInt( "count" );
            if ( position >= data.Count - Marshal.SizeOf( typeof( T ) ) + 1 ) throw new Exception( "End of Message Error" );
            List<T> output = new List<T>( );
            byte[] buffer = new byte[ Marshal.SizeOf( typeof( T ) ) ];
            for ( int i = 0 ; i < count ; ++i )
            {
                for ( int x = 0 ; x < buffer.Length ; ++x )
                    buffer[ x ] = data[ position++ ];
                output.Add( Helpers.RawDeserialize<T>( buffer ) );
            }
            return output;
        }

        public byte ReadByte( string field )
        {
            CheckFieldName( field + " [byte]" );
            if ( position >= data.Count ) throw new Exception( "End of Message Error" );
            return data[ position++ ];
        }

        public char ReadChar( string field )
        {
            CheckFieldName( field + " [char]" );
            if ( position >= data.Count ) throw new Exception( "End of Message Error" );
            return ( char )data[ position++ ];
        }

        public byte[] ReadByteArray( string field )
        {
            CheckFieldName( field + " [byte[]]" );
            int length = ReadInt( field + " length" );
            if ( position >= data.Count - length + 1 ) throw new Exception( "End of Message Error" );
            byte[] output = new byte[ length ];
            for ( int i = 0 ; i < length ; i++ )
                output[ i ] = data[ position++ ];
            return output;
        }

        public double[ , ] ReadDoubleRect( string field )
        {
            CheckFieldName( field + " [double[,]]" );
            int width = ReadInt( field + " length X" );
            int height = ReadInt( field + " length Y" );
            if ( position >= data.Count - width * height * 8 + 1 ) throw new Exception( "End of Message Error" );
            double[ , ] output = new double[ width, height ];
            for ( int y = 0 ; y < height ; ++y )
                for ( int x = 0 ; x < width ; ++x )
                    output[ y, x ] = BitConverter.ToDouble(
                        new byte[]
                            {
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ]
                            }, 0 );
            return output;
        }

        public double[] ReadDoubleArray( string field )
        {
            CheckFieldName( field + " [double[]]" );
            int length = ReadInt( field + " length" );
            if ( position >= data.Count - length * 8 + 1 ) throw new Exception( "End of Message Error" );
            double[] output = new double[ length ];
            for ( int i = 0 ; i < length ; ++i )
                output[ i ] = BitConverter.ToDouble(
                    new byte[]
                            {
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ],
                                data[ position++ ], data[ position++ ]
                            }, 0 );
            return output;
        }

        public bool ReadBool( string field )
        {
            CheckFieldName( field + " [bool]" );
            if ( position >= data.Count ) throw new Exception( "End of Message Error" );
            return data[ position++ ] != 0;
        }

        public short ReadShort( string field )
        {
            CheckFieldName( field + " [short]" );
            if ( position >= data.Count - 1 ) throw new Exception( "End of Message Error" );
            return ( short )( data[ position++ ] +
                ( data[ position++ ] << 8 ) );
            //byte[] dat = new byte[] { data[position++], data[position++] };
            //global::System.Windows.Forms.MessageBox.Show(BitConverter.ToString(dat));
            //return 0;
        }

        public int ReadInt( string field )
        {
            CheckFieldName( field + " [int]" );
            if ( position >= data.Count - 3 ) throw new Exception( "End of Message Error" );
            return ( int )( data[ position++ ] +
                ( data[ position++ ] << 8 ) +
                ( data[ position++ ] << 16 ) +
                ( data[ position++ ] << 24 ) );
        }

        public DateTime ReadDatetime( string field )
        {
            CheckFieldName( field + " [DateTime]" );
            if ( position >= data.Count - 7 ) throw new Exception( "End of Message Error" );
            return new DateTime( ( long )data[ position++ ] +
                ( ( long )data[ position++ ] << 8 ) +
                ( ( long )data[ position++ ] << 16 ) +
                ( ( long )data[ position++ ] << 24 ) +
                ( ( long )data[ position++ ] << 32 ) +
                ( ( long )data[ position++ ] << 40 ) +
                ( ( long )data[ position++ ] << 48 ) +
                ( ( long )data[ position++ ] << 56 ) );
        }
        public long ReadLong( string field )
        {
            CheckFieldName( field + " [long]" );
            if ( position >= data.Count - 7 ) throw new Exception( "End of Message Error" );
            return ( long )data[ position++ ] +
                ( ( long )data[ position++ ] << 8 ) +
                ( ( long )data[ position++ ] << 16 ) +
                ( ( long )data[ position++ ] << 24 ) +
                ( ( long )data[ position++ ] << 32 ) +
                ( ( long )data[ position++ ] << 40 ) +
                ( ( long )data[ position++ ] << 48 ) +
                ( ( long )data[ position++ ] << 56 );
        }

        public ushort ReadUShort( string field )
        {
            CheckFieldName( field + " [ushort]" );
            if ( position >= data.Count - 1 ) throw new Exception( "End of Message Error" );
            return ( ushort )( data[ position++ ] +
                ( data[ position++ ] << 8 ) );
        }

        public uint ReadUInt( string field )
        {
            CheckFieldName( field + " [uint]" );
            if ( position >= data.Count - 3 ) throw new Exception( "End of Message Error" );
            return ( uint )data[ position++ ] +
                ( ( uint )data[ position++ ] << 8 ) +
                ( ( uint )data[ position++ ] << 16 ) +
                ( ( uint )data[ position++ ] << 24 );
        }

        public ulong ReadULong( string field )
        {
            CheckFieldName( field + " [ulong]" );
            if ( position >= data.Count - 7 ) throw new Exception( "End of Message Error" );
            return ( ulong )data[ position++ ] +
                ( ( ulong )data[ position++ ] << 8 ) +
                ( ( ulong )data[ position++ ] << 16 ) +
                ( ( ulong )data[ position++ ] << 24 ) +
                ( ( ulong )data[ position++ ] << 32 ) +
                ( ( ulong )data[ position++ ] << 40 ) +
                ( ( ulong )data[ position++ ] << 48 ) +
                ( ( ulong )data[ position++ ] << 56 );
        }

        public float ReadFloat( string field )
        {
            CheckFieldName( field + " [float]" );
            if ( position >= data.Count - 3 ) throw new Exception( "End of Message Error" );
            return BitConverter.ToSingle(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
        }

        public double ReadDouble( string field )
        {
            CheckFieldName( field + " [double]" );
            if ( position >= data.Count - 7 ) throw new Exception( "End of Message Error" );
            return BitConverter.ToDouble(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++],
                    data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
        }


        public PointF ReadPointF( string field )
        {
            CheckFieldName( field + " [PointF]" );
            if ( position >= data.Count - 3 - 4 ) throw new Exception( "End of Message Error" );
            float x = BitConverter.ToSingle(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            float y = BitConverter.ToSingle(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new PointF( x, y );
        }
        public Point ReadPoint( string field )
        {
            CheckFieldName( field + " [Point]" );
            if ( position >= data.Count - 3 - 4 ) throw new Exception( "End of Message Error" );
            int x = BitConverter.ToInt32(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            int y = BitConverter.ToInt32(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new Point( x, y );
        }
        public SizeF ReadSizeF( string field )
        {
            CheckFieldName( field + " [SizeF]" );
            if ( position >= data.Count - 3 - 4 ) throw new Exception( "End of Message Error" );
            float x = BitConverter.ToSingle(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            float y = BitConverter.ToSingle(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new SizeF( x, y );
        }
        public Size ReadSize( string field )
        {
            CheckFieldName( field + " [Size]" );
            if ( position >= data.Count - 3 - 4 ) throw new Exception( "End of Message Error" );
            int x = BitConverter.ToInt32(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            int y = BitConverter.ToInt32(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new Size( x, y );
        }

        public RectangleF ReadRectangleF( string field )
        {
            CheckFieldName( field + " [RectangleF]" );
            if ( position >= data.Count - 3 - 4 - 4 - 4 ) throw new Exception( "End of Message Error" );
            float x = BitConverter.ToSingle(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            float y = BitConverter.ToSingle(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            float w = BitConverter.ToSingle(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            float h = BitConverter.ToSingle(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new RectangleF( x, y, w, h );
        }
        public Rectangle ReadRectangle( string field )
        {
            CheckFieldName( field + " [Rectangle]" );
            if ( position >= data.Count - 3 - 4 - 4 - 4 ) throw new Exception( "End of Message Error" );
            int x = BitConverter.ToInt32(
                new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            int y = BitConverter.ToInt32(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            int w = BitConverter.ToInt32(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            int h = BitConverter.ToInt32(
              new byte[] { data[position++], data[position++],
                    data[position++], data[position++] }, 0 );
            return new Rectangle( x, y, w, h );
        }

        public string ReadString( string field )
        {
            CheckFieldName( field + " [string]" );
            if ( position >= data.Count ) throw new Exception( "End of Message Error" );

            StringBuilder builder = new StringBuilder( );
            byte b = 0;
            while ( position < data.Count && ( b = data[ position++ ] ) != 0 )
                builder.Append( ( char )b );
            //position++;
            return builder.ToString( );
        }

        public T ReadObject<T>( string field ) where T : class
        {
            CheckFieldName( field + " [<" + typeof( T ).Name + ">]" );
            bool isNull = ReadBool( field + " is null" );
            if ( isNull ) return null;
            byte[] ser = ReadByteArray( field + " data" );
            return DeserializeIt( ser ) as T;
        }

        public List<byte> Data { get { return data; } }

        public byte[] ToArray( ) { return data.ToArray( ); }

        private static byte[] SerializeIt( object obj )
        {
            try
            {
                MemoryStream bout = new MemoryStream( );
                BinaryWriter objOut = new BinaryWriter( bout );
                BinaryFormatter formatter = new BinaryFormatter( );
                formatter.Serialize( objOut.BaseStream, obj );
                objOut.Close( );
                return bout.ToArray( );
            }
            catch ( IOException e )
            {
                throw new ApplicationException( "Serialization failed: " + e.Message );
            }
        }

        private static object DeserializeIt( byte[] data )
        {
            if ( data == null )
                return null;
            try
            {
                MemoryStream bin = new MemoryStream( data );
                BinaryFormatter formatter = new BinaryFormatter( );
                object obj = formatter.Deserialize( bin );
                bin.Close( );
                return obj;
            }
            catch ( IOException e )
            {
                throw new ApplicationException( "Deserialization failed: " +
                                               e.Message );
            }
            catch ( Exception e )
            {
                throw new ApplicationException( "Class not found: " + e.Message );
            }
        }

        public void Reset( )
        {
            position = 0;
        }

        public void WriteToFile( string file )
        {
            File.WriteAllBytes( file, data.ToArray( ) );
        }

        public static NetMessage ReadFromFile( string file )
        {
            return new NetMessage( new List<byte>( File.ReadAllBytes( file ) ) );
        }
    }
}